package jers.util.file;

import java.io.File;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.URL;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

import jers.JERS;
import jers.util.FileServer;
import jers.util.NetworkUtil;
import jers.util.ThreadPool;
import jers.util.Utility;


/**
 * Classe responsavel por prover um servidor HTTP para a transferencia
 * de arquivos de classes do classpath para do cliente para o servidor. 
 * O servidor RMI pode dinamicamente fazer o download dos arquivos 
 * que nao estao no servidor, fazendo o download diretamente do cliente. 
 * 
 * @see {@link SimpleHttpRequestHandle}
 * 
 * @author 
 * 		<a href="mailto:felipe.zappala@gmail.com">
 * 			Luis Felipe Zappala
 * 		</a> [ <a href="http://lncc.br/ist">ISTCC-P</a> / 2009 ]
 */
public class SimpleHttpFileServer implements FileServer, Runnable {
	
	/** Socket para conexao con o cliente. */ 
	private ServerSocket serverSocket;
	
	/** Define o flag para o estado da execucao da thread. */
	private boolean running = false;
	
	/** Diretorios de permitidos para uso no servidor */
	private Set<File> directories;
	
	/** Diretorios de permitidos para uso no servidor */
	private Set<String> jarfiles;
	
	/** Define o thread pool do sistema */
	private ThreadPool threadPool;
	
	/** Porta do servidor de arquivos */
	private int SERVER_PORT;
	
	/**
	 * O construtor padao do servidor HTTP. 
	 * 
	 * @param 
	 * 		rooDirectory - O diretorio raiz dos arquivos de classe.
	 * @param 
	 * 		port - A porta do servidor.
	 * @throws 
	 * 		IOException caso ocorra algum erro
	 */
	public SimpleHttpFileServer() {
		try {
			int defaultPort = Integer.parseInt(JERS.config
					.getProperty("jers.service.fileserver.port"));
			
			this.SERVER_PORT = NetworkUtil.getFreePort(defaultPort); 
			this.serverSocket = new ServerSocket(SERVER_PORT);
			this.directories = new HashSet<File>();
			this.jarfiles = new HashSet<String>();
			this.threadPool = JERS.factory.newThreadPool();
			this.init();
			
		} catch (Exception e) {
			throw new RuntimeException(Utility.concat(
					"Error on initialization SimpleHttpFileServer: ", 
					e.getClass().getSimpleName(), " - ", e.getMessage()), e);
		}
		
	}
	
	/**
	 * Inicializa os recursos necessarios para o servidor de arquivos
	 */
	private void init() {
		if (!loadUserFiles()) {
			loadJavaFiles();
		}
	}
	
	/**
	 * Carrega no servidor os diretorios dos arquivos definidos pelo usuario. 
	 * Retorna o status de carregamento. 
	 */
	private boolean loadUserFiles() {
		List<String> paths = Utility.fromCSV(JERS.config
				.getProperty("jers.service.fileserver.codebase"));
		for (String path : paths) {
			this.addFileDirectory(new File(path));
		}
		return !paths.isEmpty();
	}
	
	/**
	 * Carrega no servidor os diretorios dos arquivos definidos pelo 
	 * CLASSPATH do Java. Retorna o status de carregamento.  
	 */
	private boolean loadJavaFiles() {
		List<String> paths = this.getJavaDirectoriesPath();
		for (String path : paths) {
			this.addFileDirectory(new File(path));
		}
		this.addFileDirectory(new File(JERS.class.getProtectionDomain()
				.getCodeSource().getLocation().getFile()));
		
		return !paths.isEmpty(); 
	}
	
	/**
	 * Adiciona o diretorio do arquivo passado ao servidor
	 */
	protected void addFileDirectory(File file) {
		File directory = file;
		if (file.isFile()) {
			directory = file.getParentFile();
		}
		this.addDirectory(directory);
	}
	
	/**
	 * O metodo principal do servidor.
	 * Este obtem a conexao e abre uma nova thread para tratar a conexao.
	 */
	public void run() {
		this.running = true;
		
		while (running) {
			try {
				Socket socket = serverSocket.accept();
				
				JERS.log.info(Utility.concat("[SimpleHttpFileServer][IP: ",
						socket.getInetAddress().getHostAddress(), ":",
						socket.getPort(), "] accept new connection."));
				
				SimpleHttpRequestHandle request = 
						new SimpleHttpRequestHandle(this.directories, socket);
				
				if (!threadPool.isStarted()) {
					threadPool.start();
				}
				threadPool.execute(request);
				
			} catch (IOException e) {
				JERS.log.error("[SimpleHttpRequestHandle] Execution error.", e);
			}
		}
	}
	
	/**
	 * Inicia um servidor HTTP. Execute o o servidor em uma nova thread.  
	 */
	public synchronized void start() {
		JERS.log.info("[SimpleHttpFileServer] Starting the HTTP server...");
		if (!running) {
			running = true;
			
			if (!threadPool.isStarted()) {
				threadPool.start();
			}
			threadPool.execute(this);
		}
		JERS.log.info("[SimpleHttpFileServer] Server on [", getAddress(), "]");	
	}
	
	/**
	 * Para o servidor HTTP. Configura o flag de execucao para parado.
	 */
	public synchronized void stop() {
		JERS.log.info("[SimpleHttpFileServer] Stopping the HTTP server...");
		if (running) {
			running = false;
		}
	}

	private void validDirectory(File directory) {
		if (directory == null) {
			throw new IllegalArgumentException(
					"Parameter directory is null");
		}
		if (!directory.isDirectory()) {
			throw new IllegalArgumentException(
					"Parameter directory is not a directory");
		}
		if (!directory.exists()) {
			throw new IllegalArgumentException(
					"Parameter directory not exists");
		}
	}
	
	public void addDirectory(File directory) {
		validDirectory(directory);
		this.directories.add(directory);
	}

	public void removeDirectory(File directory) {
		validDirectory(directory);
		this.directories.remove(directory);
	}

	public String getAddress() {
		try {
			URL url = new URL("http", NetworkUtil.getMainIP(), SERVER_PORT, "/");
			
			String server = url.toString(); 
			StringBuilder codeBase = new StringBuilder(server).append(" ");
			
			for (String file : this.jarfiles) {
				codeBase.append(server).append(file).append(" ");
			}
			JERS.log.info("[SimpleHttpFileServer] CodeBase: ", codeBase);
			JERS.log.info("[SimpleHttpFileServer] Directories: ", directories);
			
			return codeBase.toString();
			
		} catch (Exception e) {
			JERS.log.error(Utility.concat("[SimpleHttpFileServer] ",
					"erro ao montar a URL do endereco"), e);
			throw new RuntimeException("Erro ao montar a URL do endereco", e);
		}
	}
	
	protected List<String> getJavaDirectoriesPath() {
		List<String> list = new LinkedList<String>();
		
		String pathSep = System.getProperty("path.separator");
		String path = System.getProperty("java.class.path");
		String fileSep = System.getProperty("file.separator");

		JERS.log.info("[SimpleHttpFileServer] Property [path.separator] ",
				"value [", pathSep, "]");
		JERS.log.info("[SimpleHttpFileServer] Property [java.class.path] ",
				"value [", path, "]");
		JERS.log.info("[SimpleHttpFileServer] Property [file.separator] ",
				"value [", fileSep, "]");
		
		String pt;
		String[] paths = path.split(pathSep);
		for (String p : paths) {
			if (p.toLowerCase().endsWith(".jar")) {
				String[] part = p.split(fileSep);
				pt = p.substring(0, p.indexOf(part[part.length -1]));
				String jar = p.replaceAll(pt, "");
				jarfiles.add(jar);
			} else {
				pt = p;
			}
			if (!pt.endsWith(fileSep)) {
				pt = Utility.concat(pt, fileSep);
			}
			list.add(pt);
		}
		return list;
	}

	public boolean isStarted() {
		return running;
	}

	public boolean isStopped() {
		return !running;
	}
	
}
